﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Ops.Mpc.States;
using Ops.Mpc.Presenters;
using Ops.Mpc.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using Ops.Mpc.Models;

namespace Ops.Mpc.Controllers
{
    /// <summary>
    /// Controller is used in binding models and state actions to the current state template UI.
    /// When state templates are loaded the controls in that template are given the associated controller as
    /// their default DataContext.
    /// </summary>
    [ContentProperty("Models")]
    public class Controller : IController
    {
        #region Fields & Properties
        private NamedItemCollection<State> _states = null;
        private NamedItemCollection<Model> _models = null;
        private NamedItemCollection<StateAction> _stateActions = null;
        private List<StateTransition> _stateTransitions = new List<StateTransition>();
        private IState _currentState = null;
        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler<CommandEventArgs> Command;
        private object _autorunlock = new object();
        private bool _autorunInit = false;
        private bool _isStopped = false;

        /// <summary>
        /// The current state of the controller
        /// </summary>
        public IState CurrentState
        {
            get
            {
                return this._currentState;
            }

            private set
            {
                this._currentState = value;
                this.RaisePropertyChanged("CurrentState");

            }
        }

        /// <summary>
        /// The models that the controller owns
        /// </summary>
        /// <remarks>
        /// This property is exposed for binding data
        /// </remarks>
        /// <example>
        /// <![CDATA[
        ///   <TextBox Text="{Binding Models[MyModel].FirstName}" />
        /// 
        /// ]]>
        /// 
        /// </example>
        public NamedItemCollection<Model> Models
        {
            get
            {
                return this._models;
            }

        }

        /// <summary>
        /// The states that the controller can be in
        /// </summary>
        public NamedItemCollection<State> States
        {
            get
            {
                return this._states;
            }

        }

        /// <summary>
        /// The currently avaialble state actions
        /// </summary>
        /// <remarks>
        /// This property is exposed for binding command
        /// </remarks>
        /// <example>
        /// <![CDATA[
        ///   <Button Content="Execute My State Action" Command="{Binding StateActions[MyStateAction]}" />
        /// 
        /// ]]>
        /// 
        /// </example>
        public NamedItemCollection<StateAction> StateActions
        {
            get
            {
                return this._stateActions;
            }

        }

        /// <summary>
        /// The state transistions for this controller
        /// </summary>
        public List<StateTransition> StateTransitions
        {
            get
            {
                return this._stateTransitions;
            }
        }

        /// <summary>
        /// The current state the controller is in
        /// </summary>
        IState IController.CurrentState
        {
            get
            {
                return this._currentState;
            }
        }

        /// <summary>
        /// Determines if this controller is stopped.  Controllers are stopped when their respective presenter is unloaded
        /// </summary>
        public bool IsStopped
        {
            get
            {
                return this._isStopped;
            }
        }

        /// <summary>
        /// The associated presenter for this controller
        /// </summary>
        public Presenter Presenter
        {
            get;
            set;
        }

        /// <summary>
        /// Returns the controller of the containing presenter of the associated presenter.  If there isn't a containing
        /// presenter, this property returns null.
        /// </summary>
        public IController ParentController
        {
            get
            {
               return this.GetParentController();
            }
        }

        #endregion

        #region Constructors
        public Controller()
        {
            _states = new NamedItemCollection<State>();
            _models = new NamedItemCollection<Model>();
            _stateActions = new NamedItemCollection<StateAction>();

            this._models.CollectionChanged += new NotifyCollectionChangedEventHandler(OnModelsChanged);
            this._states.CollectionChanged += new NotifyCollectionChangedEventHandler(OnStatesChanged);

            // This hanlder is used to resolve items that are not in the local collection
            this._models.ResolveItem += ResolveModel;
            this._stateActions.ResolveItem += ResolveStateAction;

        }

 


        #endregion

        #region Static Methods
        /// <summary>
        /// This method seaches thorugh the visual tree of parent controls looking for a presenter.  It returns the 
        /// controller of the first presenter it finds
        /// </summary>
        /// <param name="dependencyObject"></param>
        /// <returns>IController or null</returns>
        public static IController GetController(DependencyObject dependencyObject)
        {
            DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
            Presenter presenter = parent as Presenter;

            while (parent != null && presenter == null)
            {
                parent = VisualTreeHelper.GetParent(parent);
                presenter = parent as Presenter;
            }

            if (presenter != null)
                return presenter.Controller;
            else
                return null;



        }

        #endregion

        #region Methods
        /// <summary>
        /// This method is called by the presenter when the presenter is loaded
        /// </summary>
        public virtual void Start()
        {
            Debug.WriteLine("Controller {0} starting", this.Presenter.Name);

            if (this._currentState == null && this.States.Count > 0)
            {
                IState state = this.FindState(this.States[0].ItemName);

                if (state != null)
                    Debug.WriteLine("Controller {0} current state is {1}", this.GetType().Name, this.States[0].ItemName);
                else
                    Debug.WriteLine("Controller {0} current state is null", this.GetType().Name);

                this.CurrentState = state;

            }

            // Give each model a chance to do something interesting
            foreach (Model model in this._models)
                model.Start(this);


            // Give each state a chance to do something
            foreach (IState iState in this._states)
                iState.Start();

            // Do the auto run for the first state
            lock (_autorunlock)
            {
                if (_autorunInit == false)
                    if (this.CurrentState != null && this.CurrentState.AutoRun)
                    {
                        IStateAction iStateAction = this.CurrentState.GetFirstStateAction();
                        if (iStateAction != null)
                            iStateAction.Execute(null, new ActionResultCallback(OnAutoRunCompleted));
                    }

                _autorunInit = true;
            }

        }

        /// <summary>
        /// This method is called by the presenter when it is unloaded
        /// </summary>
        public virtual void Stop()
        {
            this._isStopped = true;

            foreach (Model model in this._models)
                model.Stop();

            foreach (IState iState in this._states)
                iState.Stop();

        }

        /// <summary>
        /// This method is called when the current state defines autorun and the first 
        /// defined state action completes
        /// </summary>
        /// <param name="stateActionResult">The result of the first defined state action when autorun is set to true</param>
        protected virtual void OnAutoRunCompleted(ActionResult stateActionResult)
        {

        }

        /// <summary>
        /// Called when the state collection changes
        /// </summary>
        /// <param name="sender">The collection that changed</param>
        /// <param name="e"></param>
        private void OnStatesChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (IState iState in e.NewItems)
                    iState.Controller = this;
            }
        }

        /// <summary>
        /// Called when the models collection changes
        /// </summary>
        /// <param name="sender">The collection that changed</param>
        /// <param name="e"></param>
        private void OnModelsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

        /// <summary>
        /// The method moves the controller to the new state
        /// </summary>
        /// <param name="stateName">The item name of the state to move to</param>
        public void SetCurrentState(string stateName)
        {
            IState state = this.FindState(stateName);

            if (state != null)
                Debug.WriteLine("Controller {0} current state is {1}", this.GetType().Name, stateName);
            else
                Debug.WriteLine("Controller {0} current state is null", this.GetType().Name);

            this.CurrentState = state;


        }

        /// <summary>
        /// Resolves the state action name with the parent presenter
        /// </summary>
        /// <param name="stateActionName">The name of the state action to resolve for</param>
        /// <returns>Returns the state action with the given name, otherwise null if not found.</returns>
        public IStateAction GetParentStateAction(string stateActionName)
        {
            return this.Presenter.GetParentStateAction(stateActionName);
        }

        private IState FindState(string stateName)
        {
            foreach(IState state in this._states)
                if(state.ItemName == stateName)
                    return state;

            return null;
        }

        /// <summary>
        /// Resolves the model with the given item name
        /// </summary>
        /// <param name="itemName">The item name of the model to resolve</param>
        /// <returns>The resolved model with the given item name or else null</returns>
        public Model GetModel(string itemName)
        {
            return this._models[itemName];

        }

        /// <summary>
        /// Resolves the state action with the given item name
        /// </summary>
        /// <param name="itemName">The item name of the state action to resolve</param>
        /// <returns>The resolved state action with the given item name or else null</returns>
        public StateAction GetStateAction(string itemName)
        {
            return this._stateActions[itemName];

        }

        /// <summary>
        /// Resolves the model with the given item name
        /// </summary>
        /// <typeparam name="T">The type of the model to return</typeparam>
        /// <param name="itemName">The item name of the model to resolve</param>
        /// <returns>The resolved model with the given item name or else null</returns>
        public T GetModel<T>(string itemName) where T : Model
        {
            return this.GetModel(itemName) as T;
        }

        /// <summary>
        /// Resolves the the 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetModel<T>() where T : Model
        {
            return this.GetModel<T>(typeof(T).Name);
        }

        private INamedItem ResolveModel(string itemName)
        {
            Debug.WriteLine("Presenter {0} controller resolving model {1}", this.Presenter.Name, itemName);

            Model model = this.GetParentModel(itemName);

            if (model == null)
            {
                Debug.WriteLine("Model with item name '{0}' not found", itemName);
                throw new ModelNotFoundException(string.Format("Model with item name '{0}' not found", itemName));
            }

            return model;
        }

        private INamedItem ResolveStateAction(string itemName)
        {
            IState iState = this.CurrentState;
            IStateAction iStateAction = null;

            Debug.WriteLine("Presenter {0} controller resolving state action {1}", this.Presenter.Name, itemName);

            if (iState != null)
                iStateAction = iState.GetStateAction(itemName, true);

            if (iStateAction == null)
            {
                Debug.WriteLine("State action with item name '{0}' not found", itemName);
                throw new StateActionNotFoundException(string.Format("State action with item name '{0}' not found", itemName));
            }

            return iStateAction;
        }


        /// <summary>
        /// This method will create a string of state item names starting from the top most containing presenter down to the calling presenter.  
        /// Each state item name is delimited by the given delimiter
        /// </summary>
        /// <param name="delimiter">The string to place between each state item name</param>
        /// <returns>Returns all the state names joined by the given delimiter</returns>
        public string GetStatePath(string delimiter)
        {
            string path = string.Empty;
            IState iState = this.Presenter.CurrentState;

            if (iState != null)
                path = iState.DisplayName == null || iState.DisplayName.Trim().Length < 1 ? iState.ItemName : iState.DisplayName;

            IController iController = this.GetParentController();

            if (iController != null)
                path = iController.GetStatePath(delimiter) + delimiter + path;

            return path;

        }

        /// <summary>
        /// This method executes the first state action in the current state if the current state has AutoRun set to true.
        /// </summary>
        public void ExecuteStateAutoRun()
        {
            lock (_autorunlock)
            {
                if (this._autorunInit)
                    if (this.CurrentState != null && this.CurrentState.AutoRun)
                    {
                        IStateAction iStateAction = this.CurrentState.GetFirstStateAction();
                        if (iStateAction != null)
                            iStateAction.Execute(null, new ActionResultCallback(OnAutoRunCompleted));
                    }
            }
        }

        protected Model GetParentModel(string modelName)
        {
            return this.Presenter.GetParentModel(modelName);
        }

        protected IController GetParentController()
        {
            Presenter presenter = this.Presenter.GetParentPresenter();

            if (presenter != null)
                return presenter.Controller;
            else
                return null;
        }

        /// <summary>
        /// This method fires any command event handlers attached to the controller.  This method can be called within a state action
        /// to fire event handlers in code-behind.
        /// </summary>
        /// <param name="commandName">The name of the command to execute</param>
        /// <param name="commandArgument">An argument to pass to the command</param>
        public void ExecuteCommand(string commandName, object commandArgument = null)
        {
            EventHandler<CommandEventArgs> commandEvent = this.Command;

            if (commandEvent != null)
                commandEvent(this, new CommandEventArgs() { CommandName = commandName, CommandArgument = commandArgument });
        }
        #endregion
    }
}

